<?php
/**
 *+------------------
 * Lflow
 *+------------------
 * Copyright (c) 2023~2030 gitee.com/liu_guan_qing All rights reserved.本版权不可删除，侵权必究
 *+------------------
 * Author: Mr.April(405784684@qq.com)
 *+------------------
 */
declare (strict_types=1);

namespace lflow\core;

use lflow\ckpt\TransitionCkpt;
use lflow\core\services\OrderServices;
use lflow\core\services\ProcessServices;
use lflow\core\services\TaskServices;
use lflow\exceptions\WorkFlowException;
use lflow\lib\trait\ClassService;
use lflow\lib\util\AssertHelper;
use lflow\lib\util\Logger;
use lflow\lib\util\ObjectHelper;
use lflow\lib\util\StringHelper;
use lflow\model\OrderModel;
use lflow\model\ProcessModel;

/**
 * 基本流程引擎实现类
 *
 * @author Mr.April
 * @since  1.0
 */
class WorkFlowEngine extends BaseServices
{

    const ADMIN = "lflow.admin";
    const ID = "lflow.orderNo";

    const AUTO = "lflow.auto";
    /**
     * 流程定义业务类
     */
    protected $processService;

    /**
     * 流程实例业务类
     */
    protected $orderService;

    /**
     * 任务业务类
     */
    protected $taskService;

    /**
     * 查询业务类
     */
    protected $queryService;

    protected $managerService;

    protected Execution $execution;

    use ClassService;

    public function __construct()
    {
        $this->orderService   = app()->make(OrderServices::class);
        $this->processService = app()->make(ProcessServices::class);
        $this->taskService    = app()->make(TaskServices::class);
        $this->queryService   = app()->make(QueryServices::class);
        $this->managerService = app()->make(ManagerServices::class);
        $this->execution      = app()->make(Execution::class);
    }

    public function execution(): Execution
    {
        return $this->execution;
    }

    public function startInstanceById(string $id, string $operator, object|string $args): OrderModel
    {
        $process = $this->processService->getProcessById($id);
        $this->processService->check($process, $id);
        return $this->startProcess($process, $operator, $args);
    }

    private function startProcess(ProcessModel $process, string $operator, object|string $args): OrderModel
    {

        try {
            $Execute = $this->executeOrder($process, $operator, $args, $parentId = '', $parentNodeName = '', $parentNodeName = 1);
            $start   = $process->getData('process_ckpt')->getStart();
            AssertHelper::notNull($start, "流程定义[name=" . $process->getData('display_name') . ", version=" . $process->getData('version') . "]没有开始节点");
            $start->exec($Execute);
            return $Execute->getOrder();
        } catch (WorkFlowException $e) {
            throw new WorkFlowException($e->getMessage());
        }

    }

    /**
     * 创建实例并启动
     *
     * @param \lflow\model\ProcessModel $process
     * @param string                    $operator
     * @param string|object             $args
     * @param string                    $parentId
     * @param string                    $parentNodeName
     *
     * @return \lflow\core\Execution
     */
    public function executeOrder(ProcessModel $process, string $operator, string|object $args, string $parentId, string $parentNodeName): Execution
    {
        //创建流程实例
        $order = $this->orderService->createOrder($process, $operator, $args, $parentId, $parentNodeName, 1);
        if (Logger::isDebugEnabled()) {
            Logger::debug("创建流程实例对象:" . json_encode($order));
        }
        $current = app()->make(Execution::class);
        $current->execution($this, $process, $order, $args);
        $current->setOperator($operator);
        return $current;
    }

    /**
     * 执行任务构造对象
     *
     * @param string $taskId
     * @param string $operator
     * @param object $args
     *
     * @return \lflow\core\Execution|null
     */
    public function execute(string $taskId, string $operator, object $args): Execution|null
    {
        $task = $this->taskService->complete($taskId, $operator, $args);
        if (Logger::isDebugEnabled()) {
            Logger::debug("任务[taskId=" . $taskId . "]已完成");
        }
        if (empty($args)) {
            $args = new \stdClass();
        }
        $order = $this->query()->getOrder($task->getData('order_id'));
        AssertHelper::notNull($order, "指定的流程实例[id=" . $task->getData('order_id') . "]已完成或不存在");
        $order->set('last_updator', $operator);
        $order->set('last_update_time', time());
        $order->save();
        //协办任务完成不产生执行对象
        if (!$task->isMajor()) {
            return null;
        }
        //参数处理将实例参数添加到args
        (object)$order_map = $order->getData('variable');
        $args      = ObjectHelper::putAll($order_map, $args);
        $process   = $this->processService->getProcessById($order->getData('process_id'));
        $execution = app()->make(Execution::class);
        $execution->execution($this, $process, $order, $args);
        $execution->setOperator($operator);
        $execution->setTask($task);
        return $execution;
    }

    /**
     * 根据任务主键ID，操作人ID，参数列表执行任务
     *
     * @param string        $taskId
     * @param string        $operator
     * @param string|object $args
     */
    public function executeTask(string $taskId, string $operator, string|object $args): void
    {
        try {
            //完成任务，并且构造执行对象
            $execution = $this->execute($taskId, $operator, $args);
            if ($execution == null) {
                return;
            }
            $ckpt = $execution->getProcess()->getData('process_ckpt');
            if (!empty($ckpt)) {
                $nodeCkpt = $ckpt->getNode($execution->getTask()->getData('task_name'));
                //将执行对象交给该任务对应的节点模型执行
                $nodeCkpt->execute($execution);
            }
        } catch (WorkFlowException $e) {
            throw new WorkFlowException($e->getMessage());
        }
    }

    /**
     * 处理指定任务
     *
     * @param string     $taskId
     * @param string|int $operator
     * @param object     $args
     * @param string     $nodeName
     *
     * @return array
     */
    public function executeAndJumpTask(string $taskId, string|int $operator, object $args, string $nodeName): array
    {
        try {
            $execution = $this->execute($taskId, $operator, $args);
            if (empty($execution)) return [];
            $ckpt = $execution->getProcess()->getData('process_ckpt');
            AssertHelper::notNull($ckpt, "当前任务未找到流程定义模型");
            if (StringHelper::isEmpty($nodeName)) {
                $newTask = $this->task()->rejectTask($ckpt, $execution->getTask());
                $execution->addTask($newTask);
            } else {
                $nodeCkpt = $ckpt->getNode($nodeName);
                AssertHelper:: notNull($nodeCkpt, "根据节点名称[" . $nodeName . "]无法找到节点模型");
                //动态创建转移对象，由转移对象执行execution实例
                $tm = new TransitionCkpt();
                $tm->setTarget($nodeCkpt);
                $tm->setEnabled(true);
                $tm->execute($execution);
            }
            return $execution->getTasks();
        } catch (WorkFlowException $e) {
            throw new WorkFlowException($e->getMessage());
        }
    }

    /**
     *  根据父执行对象启动子流程实例（用于启动子流程）
     *
     * @param \lflow\core\Execution $execution
     *
     * @return \lflow\model\OrderModel
     */
    public function startInstanceByExecution(Execution $execution): OrderModel
    {
        $process = $execution->getProcess();
        $start   = $process->getData('process_ckpt')->getStart();
        AssertHelper:: notNull($start, "流程定义[id=" . $process->getData('id') . "]没有开始节点");
        $current = $this->executeOrder($process, $execution->getOperator(), $execution->getArgs(), $execution->getParentOrder()->getData('id'), $execution->getParentNodeName());
        $start->execute($current);
        return $current->getOrder();
    }

}
